''' 
***
Modified generic daemon class
***

Author:		 http://www.jejik.com/articles/2007/02/
						a_simple_unix_linux_daemon_in_python/www.boxedice.com

License:		http://creativecommons.org/licenses/by-sa/3.0/

Changes:		23rd Jan 2009 (David Mytton <david@boxedice.com>)
				- Replaced hard coded '/dev/null in __init__ with os.devnull
				- Added OS check to conditionally remove code that doesn't
				  work on OS X
				- Added output to console on completion
				- Tidied up formatting
				11th Mar 2009 (David Mytton <david@boxedice.com>)
				- Fixed problem with daemon exiting on Python 2.4
				  (before SystemExit was part of the Exception base)
				13th Aug 2010 (David Mytton <david@boxedice.com>
				- Fixed unhandled exception if PID file is empty
'''

# Core modules
from __future__ import print_function
import atexit
import errno
import os
import sys
import time
import signal

class Daemon(object):
	"""
	A generic daemon class.

	Usage: subclass the Daemon class and override the doStart() method
	"""
	def __init__(self, pidfile, stdin=os.devnull,
				 stdout=os.devnull, stderr=os.devnull,
				 home_dir='.', umask=0o22, verbose=1,
				 use_gevent=False, use_eventlet=False):
		self.stdin = stdin
		self.stdout = stdout
		self.stderr = stderr
		self.pidfile = pidfile
		self.home_dir = home_dir
		self.verbose = verbose
		self.umask = umask
		self.daemon_alive = True
		self.use_gevent = use_gevent
		self.use_eventlet = use_eventlet
		self.doCreate()

	def log(self, *args):
		if self.verbose >= 1:
			print(*args)

	def daemonize(self):
		"""
		Do the UNIX double-fork magic, see Stevens' "Advanced
		Programming in the UNIX Environment" for details (ISBN 0201563177)
		http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
		"""
		if self.use_eventlet:
			import eventlet.tpool
			eventlet.tpool.killall()
		try:
			pid = os.fork()
			if pid > 0:
				# Exit first parent
				sys.exit(0)
		except OSError as e:
			sys.stderr.write(
				"fork #1 failed: %d (%s)\n" % (e.errno, e.strerror))
			sys.exit(1)

		# Decouple from parent environment
		os.chdir(self.home_dir)
		os.setsid()
		os.umask(self.umask)

		# Do second fork
		try:
			pid = os.fork()
			if pid > 0:
				# Exit from second parent
				sys.exit(0)
		except OSError as e:
			sys.stderr.write(
				"fork #2 failed: %d (%s)\n" % (e.errno, e.strerror))
			sys.exit(1)

		if sys.platform != 'darwin':  # This block breaks on OS X
			# Redirect standard file descriptors
			sys.stdout.flush()
			sys.stderr.flush()
			si = open(self.stdin, 'r')
			so = open(self.stdout, 'a+')
			if self.stderr:
				try:
					se = open(self.stderr, 'a+', 0)
				except ValueError:
					# Python 3 can't have unbuffered text I/O
					se = open(self.stderr, 'a+', 1)
			else:
				se = so
			os.dup2(si.fileno(), sys.stdin.fileno())
			os.dup2(so.fileno(), sys.stdout.fileno())
			os.dup2(se.fileno(), sys.stderr.fileno())

		def sigtermhandler(signum, frame):
			self.daemon_alive = False
			sys.exit()

		if self.use_gevent:
			import gevent
			gevent.reinit()
			gevent.signal(signal.SIGTERM, sigtermhandler, signal.SIGTERM, None)
			gevent.signal(signal.SIGINT, sigtermhandler, signal.SIGINT, None)
		else:
			signal.signal(signal.SIGTERM, sigtermhandler)
			signal.signal(signal.SIGINT, sigtermhandler)

		self.log("Started")

		# Write pidfile
		atexit.register(
			self.delpid)  # Make sure pid file is removed if we quit
		pid = str(os.getpid())
		open(self.pidfile, 'w+').write("%s\n" % pid)

	def delpid(self):
		try:
			# the process may fork itself again
			pid = int(open(self.pidfile, 'r').read().strip())
			if pid == os.getpid():
				os.remove(self.pidfile)
		except OSError as e:
			if e.errno == errno.ENOENT:
				pass
			else:
				raise

	def start(self, *args, **kwargs):
		"""
		Start the daemon
		"""

		self.log("Starting...")

		# Check for a pidfile to see if the daemon already runs
		try:
			pf = open(self.pidfile, 'r')
			pid = int(pf.read().strip())
			pf.close()
		except IOError:
			pid = None
		except SystemExit:
			pid = None

		if pid:
			message = "pidfile %s already exists. Is it already running?\n"
			sys.stderr.write(message % self.pidfile)
			sys.exit(1)

		# Start the daemon
		self.daemonize()
		self.doStart(*args, **kwargs)

	def stop(self):
		"""
		Stop the daemon
		"""
		if not self.doStop():
			return False
		
		if self.verbose >= 1:
			self.log("Stopping...")

		# Get the pid from the pidfile
		pid = self.get_pid()

		if not pid:
			message = "pidfile %s does not exist. Not running?\n"
			sys.stderr.write(message % self.pidfile)

			# Just to be sure. A ValueError might occur if the PID file is
			# empty but does actually exist
			if os.path.exists(self.pidfile):
				os.remove(self.pidfile)

			return  # Not an error in a restart

		# Try killing the daemon process
		try:
			i = 0
			while 1:
				os.kill(pid, signal.SIGTERM)
				time.sleep(0.1)
				i = i + 1
				if i % 10 == 0:
					os.kill(pid, signal.SIGHUP)
		except OSError as err:
			if err.errno == errno.ESRCH:
				if os.path.exists(self.pidfile):
					os.remove(self.pidfile)
			else:
				print(str(err))
				sys.exit(1)

		self.log("Stopped")

	def restart(self):
		"""
		Restart the daemon
		"""
		self.stop()
		self.start()

	def get_pid(self):
		try:
			pf = open(self.pidfile, 'r')
			pid = int(pf.read().strip())
			pf.close()
		except IOError:
			pid = None
		except SystemExit:
			pid = None
		return pid

	def is_running(self):
		pid = self.get_pid()

		if pid is None:
			self.log('Process is stopped')
			return False
		elif os.path.exists('/proc/%d' % pid):
			self.log('Process (pid %d) is running...' % pid)
			return True
		else:
			self.log('Process (pid %d) is killed' % pid)
			return False

	def doCreate(self):
		self.log('Daemon is Create!')
		
	def doStart(self):
		"""
		You should override this method when you subclass Daemon.
		It will be called after the process has been
		daemonized by start() or restart().
		"""
		raise NotImplementedError
		
	def doStop(self):
		"""
		You should override this method when you subclass Daemon.
		It will be called after the process has been
		daemonized by start() or restart().
		"""
		raise NotImplementedError
